﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NetfractionHub.Network.Protocol.BASE.Factory;

namespace NetfractionHub.Network.Protocol.BASE.Handler
{
    public class HandlerMSG : ProtocolMessageActionHandler
    {
        public HandlerMSG() : base()
        {
            ParameterCodes = new List<string>(new string[] {"PM", "ME"});
        }

        #region ProtocolMessageActionHandler Members

        public override string ProtocolActionCode
        {
            get { return "MSG"; }
        }

        public override bool HandleProtocolMessage(RemoteMachine.Node node, ProtocolMessage message, IEnumerable<ProtocolFeature> extenders)
        {
            if (!(node is RemoteMachine.Client))
                return false;

            if (message.Parameters.Count == 0)
                return false;

            List<LocalMachine.BotNode> botHandlers = new List<NetfractionHub.LocalMachine.BotNode>();

            message.Parameters = DecodeParameters(message.Parameters);

            // Message Types Direct, Echo and Feature require a SID. 
            // For the others, we'll set the node SID to the message SID just to make the code below cleaner
            if (message.SessionId != node.SessionId)
            {
                if (message.MessageType == ProtocolMessage.MessageTypeEnum.Direct ||
                    message.MessageType == ProtocolMessage.MessageTypeEnum.Echo ||
                    message.MessageType == ProtocolMessage.MessageTypeEnum.Feature)
                {
                    ConnectionManager.SendStatusMessageToNode(node,
                                                        FactorySTA.StatusSeverity.RecoverableError,
                                                        FactorySTA.StatusErrorCode.ProtocolError,
                                                        "Invalid Session Id");
                    return false;
                }
                else
                {
                    message.SessionId = node.SessionId;
                }
            }

            string messageText = string.Empty;
            bool hasMeText = false;

            messageText = GetMessageText(message);
            if (string.IsNullOrEmpty(messageText))
                return false;

            if (!IsMessageSizeAcceptable(node, messageText))
                return false;

            RemoteMachine.Client cli = node is RemoteMachine.Client ? node as RemoteMachine.Client : null;
            if (cli != null && cli.LastChatMessage != null)
            {
                if (cli.LastChatMessage.MessageText == messageText && message.TargetSessionId != message.SessionId)
                    Console.WriteLine("same last message text");

                if (DateTime.Now.Subtract(cli.LastChatMessage.MessageTime).TotalMilliseconds < 1000)
                    Console.WriteLine("messages sent less than a second apart");
            }

            var meText = message.Parameters.Where(c => c.ParameterCode == "ME");
            if (meText.Count() > 0 && meText.First().ParameterValue == "1")
                hasMeText = true;

            var pmText = message.Parameters.Where(c => c.ParameterCode == "PM");
            RemoteMachine.Node responseNode = null;
            if (pmText.Count() > 0)
            {
                if ((pmText.First() as Protocol.ProtocolParameter).ParameterValue == message.SessionId)
                    responseNode = ConnectionManager.GetNodeBySessionId(message.SessionId); // regular private message
                else
                    responseNode = ConnectionManager.GetNodeBySessionId((pmText as Protocol.ProtocolParameter).ParameterValue); // group private message;

                if (responseNode == null)
                {
                    ConnectionManager.SendStatusMessageToNode(node,
                            FactorySTA.StatusSeverity.RecoverableError,
                            FactorySTA.StatusErrorCode.ProtocolError,
                            "Unknown Reponse Session Id");
                    return false;
                }
            }
            else
            {
                // must be public chat

                // check if it's a command
                if (ConnectionManager.CheckForBotCommand(messageText, out botHandlers))
                {
                    // if it's a Hub message, assume it's not text entered by the user 
                    // e.g. a UCMD (User Command) will be sent to the hub as a Hub message,
                    // but if the user types in the command manually, it will usually be a Broadcast message
                    // NOTE: the hack here causes issues 20 lines down where we switch (message.MessageType)
                    if (message.MessageType != ProtocolMessage.MessageTypeEnum.Hub)
                        message.MessageType = ProtocolMessage.MessageTypeEnum.Echo;

                    message.TargetSessionId = botHandlers[0].SessionId; // [0] here is bad. should send to all. 
                    // if we want to display this chat in a PM, use the following line here:
                    // responseNode = ConnectionManager.GetNodeBySessionId(message.SessionId);
                    // need to check the settings to see if this is desired
                }
                else if (!Security.Permission.HasPermission(node.SecurityIdentity, typeof(Security.Permissions.PublicChatPermission)))   // we still want to allow messages to the security bot in public chat
                {
                    LocalMachine.SecurityBot.GetInstance().SendEchoMessageFromBot(node, Security.Permission.GetDeniedString(typeof(Security.Permissions.PublicChatPermission)));
                    return false;
                }
            }
            
            Protocol.ProtocolMessageFactory factory = new Protocol.BASE.Factory.FactoryMSG();
            Dictionary<int, object> factoryParams = new Dictionary<int, object>();
            factoryParams.Add((int)Protocol.BASE.Factory.FactoryMSG.ParameterEnum.SessionID, node.SessionId);

            switch (message.MessageType)
            {
                case ProtocolMessage.MessageTypeEnum.Broadcast:
                    factoryParams.Add((int)Protocol.BASE.Factory.FactoryMSG.ParameterEnum.Text, messageText);
                    break;
                case ProtocolMessage.MessageTypeEnum.Echo:
                    if (ForwardMessageToNode(node, message, messageText, responseNode, factoryParams) == false)
                        return false;
                    break;
                case ProtocolMessage.MessageTypeEnum.Hub:
                    if (ForwardMessageToNode(node, message, messageText, responseNode, factoryParams) == false)
                        return false;
                    break;
                default:
                    return false;
            }

            if (hasMeText)
                factoryParams.Add((int)Protocol.BASE.Factory.FactoryMSG.ParameterEnum.METext, (int)Protocol.BASE.Factory.FactoryMSG.METextTypesEnum.IRCNickText);

            if (cli != null)
                cli.LastChatMessage = new NetfractionHub.RemoteMachine.Client.ChatMessageProperties(messageText, DateTime.Now);

            ConnectionManager.SendChatMessage(factory.Create(message.MessageType, factoryParams));

            if (botHandlers.Count > 0)
            {
                // Do this down here so the output is displayed after the Echo
                // ConnectionManager.SendMessageToSecurityBots(node, messageText);
                // this is probably better anyway than the above line
                foreach (LocalMachine.BotNode bot in botHandlers)
                {
                    bot.HandleCommandMessage(node, messageText);
                }
            }
            return true;
        }

        private static bool ForwardMessageToNode(RemoteMachine.Node node, ProtocolMessage message, string messageText, RemoteMachine.Node responseNode, Dictionary<int, object> factoryParams)
        {
            if (ConnectionManager.GetNodeBySessionId(message.TargetSessionId) == null)
            {
                ConnectionManager.SendStatusMessageToNode(node,
                    FactorySTA.StatusSeverity.RecoverableError,
                    FactorySTA.StatusErrorCode.ProtocolError,
                    "Invalid Target Session Id");
                return false;
            }
            factoryParams.Add((int)Protocol.BASE.Factory.FactoryMSG.ParameterEnum.TargetID, message.TargetSessionId);
            factoryParams.Add((int)Protocol.BASE.Factory.FactoryMSG.ParameterEnum.Text, messageText);
            if (responseNode != null)
                factoryParams.Add((int)Protocol.BASE.Factory.FactoryMSG.ParameterEnum.PrivateMessage, responseNode.SessionId);

            return true;
        }

        private static string GetMessageText(ProtocolMessage message)
        {
            var messageParam = message.Parameters.Where(c => string.IsNullOrEmpty(c.ParameterCode));
            return (messageParam.Count() > 0 ? messageParam.First().ParameterValue : string.Empty);
        }

        private static bool IsMessageSizeAcceptable(RemoteMachine.Node node, string messageText)
        {
            bool canUseMessageLengthOverride = false;
            bool canUseMessageLinesOverride = false;

            if (IsMessageTooLong(node, messageText, out canUseMessageLengthOverride))
                return false;

            if (IsMessageTooManyLines(node, messageText, out canUseMessageLinesOverride))
                return false;

            if (canUseMessageLengthOverride)
                Console.WriteLine("performing chat length override");

            if (canUseMessageLinesOverride)
                Console.WriteLine("performing chat lines override");

            return true;
        }

        private static bool IsMessageTooLong(RemoteMachine.Node node, string messageText, out bool overrideUsed)
        {
            overrideUsed = false;
            if (messageText.Length > Settings.HubSettings.Chat.MaximumMessageLength)
            {
                if (Security.Permission.HasPermission(node.SecurityIdentity, typeof(Security.Permissions.OverrideMaximumChatLengthPermission)))
                    overrideUsed = true;
                else
                {
                    ConnectionManager.SendGenericStatusMessageToNode(node, String.Format("Message longer than {0} characters", Settings.HubSettings.Chat.MaximumMessageLength));
                    return true;
                }
            }
            return false;
        }

        private static bool IsMessageTooManyLines(RemoteMachine.Node node, string messageText, out bool overrideUsed)
        {
            overrideUsed = false;

            int lineCount = messageText.ToCharArray().Where(p => p == 0x0a).Count();
            if (messageText[messageText.Length - 1] != 0x0a)
                lineCount++;

            if (lineCount > Settings.HubSettings.Chat.MaximumMessageLines)
            {
                if (Security.Permission.HasPermission(node.SecurityIdentity, typeof(Security.Permissions.OverrideMaximumChatLinesPermission)))
                    overrideUsed = true;
                else
                {
                    ConnectionManager.SendGenericStatusMessageToNode(node, String.Format("Message lines more than {0}: {1} lines sent", Settings.HubSettings.Chat.MaximumMessageLines, lineCount));
                    return true;
                }
            }
            return false;
        }

        #endregion
    }
}
